//
// Created by twetec on 25-8-17.
//

#include "../include/network_prober/probe_detect/handle_redis_probe_detect.h"

#include <hiredis/hiredis.h>
#include <boost/json.hpp>

using namespace std::literals::chrono_literals;

namespace network_prober::probe_detect
{
void tag_invoke(boost::json::value_from_tag, boost::json::value &jv, RedisProbeDetectResultIPv4 const &c)
{
    auto& ja = jv.emplace_object();

    ja["interface"] = c.interface;
    ja["target"] = c.target;
    ja["port"] = c.port;
    ja["delay"] = c.delay;
}

HandleRedisProbeDetectIPv4::HandleRedisProbeDetectIPv4(
    std::string_view interface,
    std::string_view target,
    std::uint32_t port,
    std::string_view userName,
    std::string_view password,
    std::string_view databaseName,
    std::string_view queryStatement
)
    : m_interface(interface), m_target(target), m_port(port), m_userName(userName),
    m_password(password), m_databaseName(databaseName), m_queryStatement(queryStatement)
{
}

void HandleRedisProbeDetectIPv4::detect()
{
    // 建立连接时，开始计时
    m_detectStartTime = std::chrono::steady_clock::now();

    // 指向当前对象的shared_ptr
    auto self{shared_from_this()};

    // 发起探测
    m_onDetectedCallbackFuture = std::async(std::launch::async, [this, self] {
        // 探测结果
        RedisProbeDetectResultIPv4 result;
        result.interface = m_interface;
        result.target = m_target;
        result.port = m_port;

        // 连接配置
        redisOptions options{0};
        options.type = REDIS_CONN_TCP;  // 要求TCP方式连接
        options.endpoint.tcp.ip = m_target.c_str(); // 目标redis服务地址
        options.endpoint.tcp.port = m_port;   // 目标redis服务端口
        options.endpoint.tcp.source_addr = m_interface.c_str(); // 客户端地址
        options.options |= REDIS_OPT_PREFER_IPV4;   // 在dns时，推荐查找ipv4
        options.options |= REDIS_OPT_REUSEADDR; // 重用本地地址

        // 设置连接超时时间
        timeval connectTimeout{.tv_sec = 5, .tv_usec = 0};
        options.connect_timeout = &connectTimeout;

        // 设置命令执行的超时时间
        timeval commandTimeout{.tv_sec = 5, .tv_usec = 0};
        options.command_timeout = &commandTimeout;

        // 基础TCP连接
        redisContext* context = redisConnectWithOptions(&options);
        // 如果错误发生，context->err会被设置
        if (context == nullptr || context->err) {
            // 报错信息
            SPDLOG_INFO("Redis连接失败：{}", context->errstr);
            // 释放资源
            if (context != nullptr) redisFree(context);
            // 连接失败，延迟为-1
            result.delay = -1;
            // 回调结果
            m_onDetectedCallback(std::move(result));
            // 结束执行
            return;
        }

        // 发送指令
        redisReply* reply = static_cast<redisReply*>(redisCommand(context, "PING"));
        // 如果错误发生，reply会为null,并且context->err会被设置
        if (reply == nullptr || context->err) {
            // 报错信息
            SPDLOG_INFO("Redis指令执行失败：{}", context->errstr);
            // 释放资源
            if (context != nullptr) redisFree(context);
            // 连接失败，延迟为-1
            result.delay = -1;
            // 回调结果
            m_onDetectedCallback(std::move(result));
            // 结束执行
            return;
        }

        // 释放响应
        freeReplyObject(reply);
        // 释放资源
        redisFree(context);

        // SQL正常执行完毕,计算延迟
        result.delay = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now() - m_detectStartTime).count();
        // 回调结果
        m_onDetectedCallback(std::move(result));
    });
}

void HandleRedisProbeDetectIPv4::setOnDetectedCallback(std::function<void(RedisProbeDetectResultIPv4&&)> callback)
{
    m_onDetectedCallback = std::move(callback);
}

HandleRedisProbeDetectIPv4::~HandleRedisProbeDetectIPv4()
{

}

void tag_invoke(boost::json::value_from_tag, boost::json::value &jv, RedisProbeDetectResultIPv6 const &c)
{
    auto& ja = jv.emplace_object();

    ja["interface"] = c.interface;
    ja["target"] = c.target;
    ja["port"] = c.port;
    ja["delay"] = c.delay;
}

HandleRedisProbeDetectIPv6::HandleRedisProbeDetectIPv6(
    std::string_view interface,
    std::string_view target,
    std::uint32_t port,
    std::string_view userName,
    std::string_view password,
    std::string_view databaseName,
    std::string_view queryStatement
)
    : m_interface(interface), m_target(target), m_port(port), m_userName(userName),
    m_password(password), m_databaseName(databaseName), m_queryStatement(queryStatement)
{
}

void HandleRedisProbeDetectIPv6::detect()
{
    // 建立连接时，开始计时
    m_detectStartTime = std::chrono::steady_clock::now();

    // 指向当前对象的shared_ptr
    auto self{shared_from_this()};

    // 发起探测
    m_onDetectedCallbackFuture = std::async(std::launch::async, [this, self] {
        // 探测结果
        RedisProbeDetectResultIPv6 result;
        result.interface = m_interface;
        result.target = m_target;
        result.port = m_port;

        // 连接配置
        redisOptions options{0};
        options.type = REDIS_CONN_TCP;  // 要求TCP方式连接
        options.endpoint.tcp.ip = m_target.c_str(); // 目标redis服务地址
        options.endpoint.tcp.port = m_port;   // 目标redis服务端口
        // options.endpoint.tcp.source_addr = m_interface.c_str(); // 客户端地址
        options.options |= REDIS_OPT_PREFER_IPV6;   // 在dns时，推荐查找ipv6
        options.options |= REDIS_OPT_REUSEADDR; // 重用本地地址

        // 设置连接超时时间
        timeval connectTimeout{.tv_sec = 5, .tv_usec = 0};
        options.connect_timeout = &connectTimeout;

        // 设置命令执行的超时时间
        timeval commandTimeout{.tv_sec = 5, .tv_usec = 0};
        options.command_timeout = &commandTimeout;

        // 基础TCP连接
        redisContext* context = redisConnectWithOptions(&options);
        if (context == nullptr || context->err) {
            // 报错信息
            SPDLOG_INFO("Redis连接失败：{}", context->errstr);
            // 释放资源
            if (context != nullptr) redisFree(context);
            // 连接失败，延迟为-1
            result.delay = -1;
            // 回调结果
            m_onDetectedCallback(std::move(result));
            // 结束执行
            return;
        }

        // 发送指令
        redisReply* reply = static_cast<redisReply*>(redisCommand(context, "PING"));
        // 如果错误发生，reply会为null,并且context->err会被设置
        if (reply == nullptr || context->err) {
            // 报错信息
            SPDLOG_INFO("Redis指令执行失败：{}", context->errstr);
            // 释放资源
            if (context != nullptr) redisFree(context);
            // 连接失败，延迟为-1
            result.delay = -1;
            // 回调结果
            m_onDetectedCallback(std::move(result));
            // 结束执行
            return;
        }

        // 释放响应
        freeReplyObject(reply);
        // 释放资源
        redisFree(context);

        // SQL正常执行完毕,计算延迟
        result.delay = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now() - m_detectStartTime).count();
        // 回调结果
        m_onDetectedCallback(std::move(result));
    });
}

void HandleRedisProbeDetectIPv6::setOnDetectedCallback(std::function<void(RedisProbeDetectResultIPv6&&)> callback)
{
    m_onDetectedCallback = std::move(callback);
}

HandleRedisProbeDetectIPv6::~HandleRedisProbeDetectIPv6()
{

}

}